package com.ihemou.auth.service;

import cn.hutool.core.codec.Base64;
import cn.hutool.crypto.Mode;
import cn.hutool.crypto.Padding;
import cn.hutool.crypto.symmetric.AES;
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.ihemou.auth.form.LoginBody;
import com.ihemou.common.core.constant.Constants;
import com.ihemou.common.core.constant.UserConstants;
import com.ihemou.common.core.domain.R;
import com.ihemou.common.core.enums.UserStatus;
import com.ihemou.common.core.exception.ServiceException;
import com.ihemou.common.core.utils.ServletUtils;
import com.ihemou.common.core.utils.StringUtils;
import com.ihemou.common.core.utils.ip.IpUtils;
import com.ihemou.common.security.utils.SecurityUtils;
import com.ihemou.system.dto.SysLoginInfoDTO;
import com.ihemou.system.dto.SysSocialAppConfigDTO;
import com.ihemou.system.dto.SysUserDTO;
import com.ihemou.system.dto.UserInfo;
import com.ihemou.system.dto.WxUserInfoDTO;
import com.ihemou.system.feign.RemoteLogService;
import com.ihemou.system.feign.RemoteUserService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.HashMap;
import java.util.Map;

/**
 * 登录校验方法
 *
 * @author ruoyi
 */
@Component
public class SysLoginService {

    private static final Logger log = LoggerFactory.getLogger(SysLoginService.class);

    @Autowired
    private RemoteLogService remoteLogService;

    @Autowired
    private RemoteUserService remoteUserService;

    /**
     * 登录
     */
    public UserInfo login(String username, String password) {
        // 用户名或密码为空 错误
        if (StringUtils.isAnyBlank(username, password)) {
            recordLoginInfo(username, Constants.LOGIN_FAIL, "用户/密码必须填写");
            throw new ServiceException("用户/密码必须填写");
        }
        // 密码如果不在指定范围内 错误
        if (password.length() < UserConstants.PASSWORD_MIN_LENGTH
                || password.length() > UserConstants.PASSWORD_MAX_LENGTH) {
            recordLoginInfo(username, Constants.LOGIN_FAIL, "用户密码不在指定范围");
            throw new ServiceException("用户密码不在指定范围");
        }
        // 用户名不在指定范围内 错误
        if (username.length() < UserConstants.USERNAME_MIN_LENGTH
                || username.length() > UserConstants.USERNAME_MAX_LENGTH) {
            recordLoginInfo(username, Constants.LOGIN_FAIL, "用户名不在指定范围");
            throw new ServiceException("用户名不在指定范围");
        }
        // 查询用户信息
        R<UserInfo> userResult = remoteUserService.getUserInfo(username, Constants.REQUEST_INNER);

        if (StringUtils.isNull(userResult) || StringUtils.isNull(userResult.getData())) {
            recordLoginInfo(username, Constants.LOGIN_FAIL, "登录用户不存在");
            throw new ServiceException("登录用户：" + username + " 不存在");
        }

        if (R.FAIL == userResult.getCode()) {
            throw new ServiceException(userResult.getMsg());
        }
        UserInfo userInfo = userResult.getData();
        SysUserDTO user = userResult.getData().getSysUser();
        checkUserStatus(userInfo);
        if (!SecurityUtils.matchesPassword(password, user.getPassword())) {
            recordLoginInfo(username, Constants.LOGIN_FAIL, "用户密码错误");
            throw new ServiceException("用户不存在/密码错误");
        }
        recordLoginInfo(username, Constants.LOGIN_SUCCESS, "登录成功");
        return userInfo;
    }

    public void logout(String loginName) {
        recordLoginInfo(loginName, Constants.LOGOUT, "退出成功");
    }

    /**
     * 微信登录
     *
     * @param loginBody 登录的信息
     */
    public UserInfo weixinLogin(LoginBody loginBody) {

        // 获取前端传过来的数据start
        String code = loginBody.getCode();
        String iv = loginBody.getIv();
        String encryptedData = loginBody.getEncryptedData();
        // 获取前端传过来的数据end
        // 获取openid和session_key  start
        Map<String, String> stringStringMap = getOpenIdAndSessionKey(code, loginBody.getProductCode());
        String openid = stringStringMap.get("openid");
        String sessionKey = stringStringMap.get("session_key");
        String appCode = stringStringMap.get("app_code");
        String deptId = stringStringMap.get("dept_id");
        String examineUser = stringStringMap.get("examine_user");
        // 获取openid和session_key  end
        log.info("微信小程序授权请求的参数:" + JSONObject.toJSONString(stringStringMap));

        try {
            if (StringUtils.isNotEmpty(encryptedData) && StringUtils.isNotEmpty(iv)) {
                // 解密数据
                byte[] resultByte = decryptInfoMethod(encryptedData, sessionKey, iv);

                if (null != resultByte) {
                    // 解密数据转成wxUserInfoDTO
//                String userInfo = new String(resultByte, "UTF-8");
                    WxUserInfoDTO wxUserInfoDTO = JSONObject.parseObject(resultByte, WxUserInfoDTO.class);
                    if (StringUtils.isNotEmpty(wxUserInfoDTO.getNickName())) {
                        if (wxUserInfoDTO.getGender() == 0) {
                            // 系统里的 2是未知，0是男，1是女;  微信 0是未知，1是男，2是女
                            wxUserInfoDTO.setGender(2);
                        } else {
                            wxUserInfoDTO.setGender(wxUserInfoDTO.getGender() - 1);
                        }
                    }
                    wxUserInfoDTO.setExamineUser(examineUser);
                    wxUserInfoDTO.setOpenId(openid);
                    wxUserInfoDTO.setAppCode(appCode);
                    wxUserInfoDTO.setDeptId(deptId);
                    // 保存或更新用户
                    R<UserInfo> userResult = remoteUserService.addSocialUser(wxUserInfoDTO, Constants.REQUEST_INNER);
                    // 检查用户状态
                    checkUserStatus(userResult.getData());
                    recordLoginInfo(openid, Constants.LOGIN_SUCCESS, "登录成功");
                    return userResult.getData();
                } else {
                    log.error("微信登录解密用户信息失败");
                    throw new ServiceException("微信登录解析用户信息失败!");
                }
            } else {
                // 自动登录
                WxUserInfoDTO wxUserInfoDTO = new WxUserInfoDTO();
                wxUserInfoDTO.setOpenId(openid);
                wxUserInfoDTO.setAppCode(appCode);
                R<UserInfo> userResult = remoteUserService.findSocialUser(wxUserInfoDTO, Constants.REQUEST_INNER);
                // 检查用户状态
                checkUserStatus(userResult.getData());
                recordLoginInfo(userResult.getData().getUsername(), Constants.LOGIN_SUCCESS, "登录成功");
                return userResult.getData();
            }
        } catch (ServiceException b) {
            recordLoginInfo(openid, Constants.LOGIN_FAIL, "微信登录失败");
            throw b;
        } catch (Exception e) {
            log.error("微信登录解密用户信息异常");
            recordLoginInfo(openid, Constants.LOGIN_FAIL, "微信登录解密用户信息失败");
            throw new ServiceException("微信授权信息登录失败!");
        }
    }

    private Map<String, String> getOpenIdAndSessionKey(String code, String miniCode) {

        // 查询配置的小程序的密钥
        R<SysSocialAppConfigDTO> socialAppConfigDTO = remoteUserService.getAppConfig(miniCode, Constants.REQUEST_INNER);
        if (StringUtils.isNull(socialAppConfigDTO) || StringUtils.isNull(socialAppConfigDTO.getData())) {
            throw new ServiceException("微信授权信息登录失败!");
        } else {
            // 构建请求微信接口获取openid和session_key的所需参数start
            Map<String, Object> pa = new HashMap<>(12);
            pa.put("appid", socialAppConfigDTO.getData().getAppId());
            pa.put("secret", socialAppConfigDTO.getData().getSecret());
            pa.put("js_code", code);
            pa.put("grant_type", "authorization_code");
            String openidUrl = "https://api.weixin.qq.com/sns/jscode2session";

            // 构建请求微信接口获取openid和session_key的所需参数end
            log.info("微信小程序授权请求的参数:" + JSONObject.toJSONString(pa));
            // 调用微信接口获取openid和session_key start
            String body = HttpUtil.post(openidUrl, pa);
            JSONObject jsonObject = JSON.parseObject(body);
            String openid = jsonObject.getString("openid");
            String sessionKey = jsonObject.getString("session_key");
            // 调用微信接口获取openid和session_key end

            // 对openid和session_key进行判断start
            if (openid == null || sessionKey == null) {
                log.error("微信小程序授权失败，原因是:" + JSONObject.toJSONString(jsonObject));
                throw new ServiceException("微信授权信息登录失败!");
            }
            // 对openid和session_key进行判断end

            Map<String, String> result = new HashMap<>(6);
            result.put("openid", openid);
            result.put("session_key", sessionKey);
            result.put("app_code", socialAppConfigDTO.getData().getAppCode());
            result.put("dept_id", socialAppConfigDTO.getData().getDeptId());
            result.put("examine_user", socialAppConfigDTO.getData().getExamineUser());
            return result;
        }
    }

    /**
     * 解密用户信息
     *
     * @param encryptedData 加密数据
     * @param sessionKey    密钥
     * @param iv            向量
     * @return
     */
    private byte[] decryptInfoMethod(String encryptedData, String sessionKey, String iv) {

        try {
            // 解密用户信息start
            // 被加密的数据
            byte[] encryptedDataB = Base64.decode(encryptedData);
            // 加密秘钥
            byte[] sessionKeyB = Base64.decode(sessionKey);
            // 偏移量
            byte[] ivB = Base64.decode(iv);
            AES aes = new AES(Mode.CBC, Padding.PKCS5Padding, sessionKeyB, ivB);
            byte[] resultByte = aes.decrypt(encryptedDataB);
            // 解密用户信息end
            return resultByte;

        } catch (Exception e) {
            log.error("解密微信信息出错，错误信息如下：", e);
            throw new ServiceException("微信授权信息解析失败!");
        }
    }

    /**
     * 检查用户的状态
     * @param userInfo 用户信息
     */
    private void checkUserStatus(UserInfo userInfo) {
        if (userInfo != null) {
            SysUserDTO user = userInfo.getSysUser();
            if (UserStatus.DELETED.getCode().equals(user.getDelFlag())) {
                recordLoginInfo(user.getUserName(), Constants.LOGIN_FAIL, "对不起，您的账号已被删除");
                throw new ServiceException("对不起，您的账号：" + user.getUserName() + " 已被删除");
            }
            if (UserStatus.DISABLE.getCode().equals(user.getStatus())) {
                recordLoginInfo(user.getUserName(), Constants.LOGIN_FAIL, "用户已停用，请联系管理员");
                throw new ServiceException("对不起，您的账号：" + user.getUserName() + " 已停用");
            }
            if (UserStatus.APPROVE.getCode().equals(user.getStatus())) {
                recordLoginInfo(user.getUserName(), Constants.LOGIN_FAIL, "用户已停用，请联系管理员");
                throw new ServiceException("对不起，您的账号：" + user.getUserName() + " 需要审核");
            }
        }
    }

    /**
     * 注册
     */
    public void register(String username, String password)
    {
        // 用户名或密码为空 错误
        if (StringUtils.isAnyBlank(username, password))
        {
            throw new ServiceException("用户/密码必须填写");
        }
        if (username.length() < UserConstants.USERNAME_MIN_LENGTH
                || username.length() > UserConstants.USERNAME_MAX_LENGTH)
        {
            throw new ServiceException("账户长度必须在2到20个字符之间");
        }
        if (password.length() < UserConstants.PASSWORD_MIN_LENGTH
                || password.length() > UserConstants.PASSWORD_MAX_LENGTH)
        {
            throw new ServiceException("密码长度必须在5到20个字符之间");
        }

        // 注册用户信息
        SysUserDTO sysUser = new SysUserDTO();
        sysUser.setUserName(username);
        sysUser.setNickName(username);
        sysUser.setPassword(SecurityUtils.encryptPassword(password));
        R<?> registerResult = remoteUserService.registerUserInfo(sysUser, Constants.REQUEST_INNER);

        if (R.FAIL == registerResult.getCode())
        {
            throw new ServiceException(registerResult.getMsg());
        }
        recordLoginInfo(username, Constants.REGISTER, "注册成功");
    }

    /**
     * 记录登录信息
     *
     * @param username 用户名
     * @param status 状态
     * @param message 消息内容
     * @return
     */
    public void recordLoginInfo(String username, String status, String message)
    {
        SysLoginInfoDTO loginInfo = new SysLoginInfoDTO();
        loginInfo.setUserName(username);
        loginInfo.setIpaddr(IpUtils.getIpAddr(ServletUtils.getRequest()));
        loginInfo.setMsg(message);
        // 日志状态
        if (StringUtils.equalsAny(status, Constants.LOGIN_SUCCESS, Constants.LOGOUT, Constants.REGISTER))
        {
            loginInfo.setStatus("0");
        }
        else if (Constants.LOGIN_FAIL.equals(status))
        {
            loginInfo.setStatus("1");
        }
        remoteLogService.saveLogininfor(loginInfo, Constants.REQUEST_INNER);
    }

}
